#ifndef __VFS_H_
#define __VFS_H_

#include <type.h>
#include <fs/dirent.h>
#include <ds/list.h>
#include <ds/mutex.h>
#include <ds/hstr.h>
#include <ds/hashtable.h>
#include <device/device.h>
#include <status.h>
#include <fflags.h>

#define FS_FD_MAX 32
#define FS_NAME_MAX 127

#define READ 0
#define WRITE 1

#define DEVCH 0x1
#define DEVBLK 0x2

#define MAJOR(x) ((x) >> 8)
#define MINOR(x) ((x) & 0xFF)

#define FS_WALK_MKPARENT 0x1
#define FS_WALK_FDPARENT 0x2

#define FS_HASHTABLE_BITS 10
#define FS_HASHTABLE_SIZE (1 << FS_HASHTABLE_BITS)
#define FS_HASH_MASK (FS_HASHTABLE_SIZE - 1)
#define FS_HASHBITS (32 - FS_HASHTABLE_BITS)

#define FS_VALID_CHAR(c)                                       		\
    (('A' <= (c) && (c) <= 'Z') || ('a' <= (c) && (c) <= 'z') ||	\
     ('0' <= (c) && (c) <= '9') || (c) == '.' || (c) == '_' ||		\
     (c) == '-' || (c) == ' ')

#define unlock_inode(inode) mutex_unlock(&inode->lock)
#define lock_inode(inode)                                                      \
	({                                                                         \
		mutex_lock(&inode->lock);                                              \
		lru_use_one(inode_lru, &inode->lru);                                   \
	})

#define unlock_dentry(dentry) mutex_unlock(&dentry->lock)
#define lock_dentry(dentry)                                                      \
	({                                                                         \
		mutex_lock(&dentry->lock);                                              \
		lru_use_one(dentry_lru, &dentry->lru);                                   \
	})

struct inode_operations;
struct file_operations;
struct mount_point;
struct inode;
struct dentry;
struct file;
struct filesystem;
struct super_block;
struct file_descriptor_table;

typedef struct page_cache {
	struct inode* parent;
	lnode_t pages;
	lnode_t dirty;
	u32 n_dirty;
	u32 n_pages;
} pcache_t;

typedef struct page_cache_page {
	lnode_t page_list;
	lnode_t dirty_list;
	lnode_t lru;
	struct pcache* master;
	void* page;
	u32 flags;
	u32 fpos;
} pc_page_t;

typedef struct mount_point {
	mutex_t lock;
	lnode_t list;
	lnode_t sub_mnt;
	struct mount_point* parent;
	struct dentry* mnt_point;
	struct super_block* sb;
	u32 busy_count;
	u32 flags;
} mount_t;

typedef struct inode {
	mutex_t lock;
	u32 id;
	struct super_block* sb;
	lnode_t lru;
	hlnode_t hash_list;
	u32 type;
	u8 i_mode;
	u32 i_dev;
	u32 open_count;
	u32 link_count;
	u32 fsize;
	pcache_t* pcache;
	void* data;
	struct inode_operations* iops;
	struct file_operations* fops;
} inode_t;

typedef struct dentry {
	mutex_t lock;
	lnode_t children;
	lnode_t siblings;
	lnode_t lru;
	hlnode_t hash_list;
	hstr_t name;
	inode_t* inode;
	struct dentry* parent;
	struct super_block* sb;
	mount_t* mnt;
	atomic_uint ref_count;
	void* data;
} dentry_t;

typedef struct file {
	lnode_t list;
	// u32 flags;
	atomic_uint ref_count;
	inode_t* inode;
	dentry_t* dentry;
	u32 f_pos;
	struct file_operations* ops;
} file_t;

typedef struct filesystem {
	lnode_t fs_list;
	hstr_t fs_name;
	int id;
	u32 type;
	int (*mount)(struct super_block* this, dentry_t* mnt_point);
	int (*umount)(struct super_block* this);
} fs_t;

typedef struct super_block {
	lnode_t sb_list;
	device_t* dev;
	dentry_t* root;
	fs_t* fs;
	hbkt_t* inode_cache;
	void* data;
	void (*init_inode)(struct super_block* this, inode_t* inode);
} sb_t;

typedef struct file_descriptor {
	file_t* file;
	int flags;
} fd_t;

typedef struct file_descriptor_table {
	fd_t* fds[FS_FD_MAX];
} fd_table_t;

typedef struct inode_operations {
	int (*create)(inode_t* this, dentry_t* dentry);
	int (*open)(inode_t* this, file_t* file);
	int (*mkdir)(inode_t* this, dentry_t* dentry);
	int (*rmdir)(inode_t* this, dentry_t* dentry);
	int (*lookup)(inode_t* this, dentry_t* dentry);
	int (*sync)(inode_t* this);
} inode_ops;

typedef struct file_operations {
	int (*read)(inode_t* this, void* buf, u32 count, u32 fpos);
	int (*write)(inode_t* this, void* buf, u32 count, u32 fpos);
	int (*seek)(inode_t* this, size_t offset);
	int (*close)(file_t* this);
	int (*sync)(file_t* this);
	int (*readdir)(file_t* this, dirent_t* dirent);
} f_ops;

extern f_ops default_file_ops;
extern inode_ops default_inode_ops;

extern hstr_t fs_path_dot;
extern hstr_t fs_path_dotdot;
extern dentry_t* fs_root;

void fs_init();

void fs_install_default();
fs_t* fs_alloc_fs(char* name, u32 name_len);
void fs_register(fs_t* fs);
fs_t* fs_find(const char* name);

int fs_mount_root(const char* fs_name, device_t* device);
int fs_mount(char* target, char* fs_name, device_t* device, int options);
int fs_umount(char* target);
int fs_mount_at(char* fs_name, device_t* device, dentry_t* mnt_point, int options);
int fs_umount_at(dentry_t* mnt_point);

void mount_busy(mount_t* mnt);
void mount_free(mount_t* mnt);

void pcache_init(pcache_t* pcache);

dentry_t* fs_dcache_lookup(dentry_t* parent, hstr_t* name);
void fs_dcache_add(dentry_t* parent, dentry_t* dentry);
void fs_dcache_remove(dentry_t* dentry);
void fs_dcache_rehash(dentry_t* parent, dentry_t* dentry);

int fs_alloc_fd(int* fd);
int fs_dup_fd(fd_t* old, fd_t** new);
int fs_getfd(int fd, fd_t** fd_s);

dentry_t* fs_alloc_dentry(dentry_t* parent, hstr_t* name);
void fs_free_dentry(dentry_t* dentry);

sb_t* fs_alloc_sb();
void fs_free_sb(sb_t* sb);

inode_t* fs_alloc_inode(sb_t* sb);
void fs_inode_addhash(inode_t* inode);
inode_t* fs_find_inode(sb_t* sb, u32 id);
void fs_assign_inode(dentry_t* dentry, inode_t* inode);
void fs_free_inode(inode_t* inode);

int fs_open(dentry_t* dentry, file_t** file);
int fs_close(file_t* file);
int fs_path_walk(const char* path, dentry_t* start, dentry_t** dentry, hstr_t* out, int flags);

int default_file_close(file_t* file);
int default_inode_open(inode_t* this, file_t* file);
int default_file_read(inode_t* inode, void* buffer, size_t len, size_t fpos);
int default_file_write(inode_t* inode, void* buffer, size_t len, size_t fpos);
int default_inode_lookup(inode_t* this, dentry_t* dentry);
int default_inode_rename(inode_t* from_inode, dentry_t* from_dentry, dentry_t* to_dentry);
int default_inode_sync(inode_t* this);
int default_inode_rmdir(inode_t* this, dentry_t* dir);
int default_inode_mkdir(inode_t* this, dentry_t* dir);
#endif // __VFS_H_